﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using Unity.Errors.types;
using DbGenerator.Factory.events;
using System.Text;
using DbGenerator.Resources.dicts;
using DbGenerator.Resources.text;
using DbGenerator.Model.units.table;
using DbGenerator.Model.units.constraint.types;
using DbGenerator.Model.attributes.types;
using DbGenerator.Model.units.column;
using DbGenerator.Model.attributes.root;
using System.Data.SqlClient;
using DbGenerator.Utilities;
using DbGenerator.Factory.concrete.rsc;
using System.Data;

namespace DbGenerator.Factory.generator
{
    public class TDbFactory : ADbFactory
    {
        #region private methods
        private void PopulateTable(TTable t, PropertyInfo[] piColumns)
        {
            //loop between columns of one table(t)
            int limit = piColumns.Length;
            for (int i = 0; i < limit; i++)
            {
                //Column mark(only once)
                TColumnAttribute columnMark =
                    (piColumns[i]
                    .GetCustomAttributes(typeof(TColumnAttribute), false) as TColumnAttribute[])[0];
                TColumn c = new TColumn(columnMark) { BaseTable = t };
                t.AddColumn(c);
            }

            //Table filled; now assign cust. attrs:
            int columnCount = piColumns.Length;
            for (int i = 0; i < columnCount; i++)
            {
                SetCustomAttributes(t, piColumns[i]);
            }
        }
        private void SetCustomAttributes(TTable t, PropertyInfo piColumn)
        {
            TColumnAttribute columnAttribute = (piColumn
                .GetCustomAttributes(typeof(TColumnAttribute), false) as TColumnAttribute[])[0];
            TConstraintAttributeBase[] constraintAttributes =
                (piColumn
                    .GetCustomAttributes(typeof(TConstraintAttributeBase), true) as TConstraintAttributeBase[]);

            //Loop in table constraints of currentColumn
            int constraintAttributeCount = constraintAttributes.Length;
            for (int i = 0; i < constraintAttributeCount; i++)
            {
                //if it is a pk add:
                if (constraintAttributes[i] is TPKAttribute)
                {
                    TPKAttribute attr = constraintAttributes[i] as TPKAttribute;
                    t.GetColumnByName(columnAttribute.ColumnName)
                        .SetPKConstraint(new TPKConstraint(attr));
                }
                else if (constraintAttributes[i] is TUQAttribute)
                {
                    TUQAttribute attr = constraintAttributes[i] as TUQAttribute;
                    t.GetColumnByName(columnAttribute.ColumnName)
                       .SetUQConstraint(new TUQConstraint(attr));
                }
                else if (constraintAttributes[i] is TDKAttribute)
                {
                    TDKAttribute attr = constraintAttributes[i] as TDKAttribute;
                    t.GetColumnByName(columnAttribute.ColumnName)
                       .SetDKConstraint(new TDKConstraint(attr));
                }
                else if (constraintAttributes[i] is TCKAttribute)
                {
                    TCKAttribute attr = constraintAttributes[i] as TCKAttribute;
                    t.GetColumnByName(columnAttribute.ColumnName)
                       .SetCKConstraint(new TCKConstraint(attr));
                }
                else if (constraintAttributes[i] is TFKAttribute)
                {
                    TFKAttribute attr = constraintAttributes[i] as TFKAttribute;
                    t.GetColumnByName(columnAttribute.ColumnName)
                       .SetFKConstraint(new TFKConstraint(attr));
                }
            }
        }
        #endregion

        #region handler methods
        protected override void SelectConcreteAssembly(object sender, TFactoryArgs args)
        {
            Assembly asmConcrete = null;
            asmConcrete = Assembly.LoadFrom(args.FullEntityPath);

            SErrorManager.CustomError()
                 .Assert(asmConcrete != null, new TErrorInfo(GetType(), "SelectConcreteAssembly")
                 , "Assembly {0} load unsuccessful.", args.AssemblyName);

            args.Dispatch = asmConcrete;
        }
        protected override void ConstructModel(object sender, TFactoryArgs args)
        {

            Assembly asmConcreteModule = args.Dispatch as Assembly;
            Type[] asmEntities = asmConcreteModule.GetTypes();
            int limit = asmEntities.Length;
            List<TTable> tables = new List<TTable>();

            for (int i = 0; i < limit; i++)
            {
                //loop in assemblies
                Type asmE = asmEntities[i];
                TEntityAttribute tEntityAttribute = null;
                //Find table name by tablenameattribute
                object[] tableEntityAttributes = asmE.GetCustomAttributes(typeof(TEntityAttribute), false);

                SErrorManager.CustomError()
                    .Assert(tableEntityAttributes.Length == 1
                    , new TErrorInfo(GetType(), "ConstructModel")
                    , "tableEntityAttributes for specified module {0} defined more than once or not found."
                    + "\nPlease add [TEntityAttribute(\"{0}\")] once only.", asmE.Name);

                tEntityAttribute = tableEntityAttributes[0] as TEntityAttribute;
                //Construct table object:
                TTable t = new TTable(new TTableSign(tEntityAttribute.TableName)
                    , tEntityAttribute.TableSchema
                    , args.Database);

                //Collect columns:
                PropertyInfo[] piColumns = asmE.GetProperties();
                PopulateTable(t, piColumns);
                tables.Add(t);
            }
            //Database constructed.
            args.Database.AddRangeTable(tables.ToArray());
        }
        protected override void ConstructRelationsModel(object sender, TFactoryArgs args)
        {
            TColumn[] completePKColumnList = args.Database.CollectAllPKColumns();
            TColumn[] completeFKColumnList = args.Database.CollectAllFKColumns();
            //Complete sets constructed.

            //Now connect between tables:
            int fkColumnCount = completeFKColumnList.Length;
            int pkColumnCount = completePKColumnList.Length;
            for (int i = 0; i < fkColumnCount; i++)
            {
                TColumn curFKColumn = completeFKColumnList[i];
                if (curFKColumn.FKConstraint != null)
                    for (int j = 0; j < pkColumnCount; j++)
                    {
                        if (curFKColumn.FKConstraint.CustomAttribute.ReferencedSchemaName == completePKColumnList[j].BaseTable.TableSchema
                            && curFKColumn.FKConstraint.CustomAttribute.ReferencedTableName == completePKColumnList[j].BaseTable.TableSign.TableName
                            && curFKColumn.FKConstraint.CustomAttribute.ReferencedColumnName == completePKColumnList[j].ColumnInfo.ColumnName)
                        {
                            curFKColumn.ConnectTo(completePKColumnList[j].BaseTable);
                        }
                    }
            }

        }
        protected override void CreateDatabase(object sender, TFactoryArgs args)
        {
            if (DatabaseExists(args))
            {
                DropDatabase(args);
                CreateDatabase(args);
            }
            else
                CreateDatabase(args);
        }
        protected override void CreateSchemas(object sender, TFactoryArgs args)
        {
            SqlCommand command = new SqlCommand();
            command.Connection = args.MasterConnection;

            if (command.Connection.State == System.Data.ConnectionState.Closed)
                command.Connection.Open();

            //CREATE SCHEMA statement must be first command in the batch:
            command.CommandText = String.Format("USE [{0}]", args.Database.DatabaseName);
            command.ExecuteNonQuery();

            int tableCount = args.Database.Tables.Count;
            for (int i = 0; i < tableCount; i++)
            {
                command.CommandText =
                    String.Format("CREATE SCHEMA {1}"
                            , args.Database.DatabaseName
                             , args.Database.Tables[i].TableSchema);
                try
                {
                    command.ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    continue;
                }
            }
            if (command.Connection.State == ConnectionState.Open)
                command.Connection.Close();
        }
        protected override void DropExistingForeignKeys(object sender, TFactoryArgs args)
        {
#warning code this.

        }
        protected override void ConstructDbScript(object sender, TFactoryArgs args)
        {
            StringBuilder sb = new StringBuilder();

            //Loop between args.Database.Tables
            int tblCount = args.Database.Tables.Count;
            for (int i = 0; i < tblCount; i++)
            {
                TTable currentTable = args.Database.Tables[i];
                sb.Append(SDbCreateStrings.TableCreateHeader);
                sb.Append(
                    SDbGenerationUtilities
                    .GenerateIfNotExistsSentence(currentTable.TableSign.TableName
                    , ESqlObjectType.TABLE));
                //Start of create table block:
                sb.Append(SDbCreateStrings.BlockBegin);

                sb.Append(currentTable.CreateTableScript());

                sb.Append(SDbCreateStrings.BlockEnd);
            }
            sb.Append(args.Database.RelationsScript());
            args.Dispatch = sb;
        }
        protected override void CommitToDatabase(object sender, TFactoryArgs args)
        {

            SqlCommand script = new SqlCommand(args.Dispatch.ToString());
            script.Connection = args.MasterConnection;
            try
            {
                script.Connection.Open();
                try
                {
                    script.ExecuteNonQuery();
                }
                catch (Exception exExec)
                {
                    SErrorManager.Exception()
                        .Throw(exExec, new TErrorInfo(GetType(), "CommitToDatabase")
                        , "ExecuteNonQuery Failed.");
                }
                finally
                {
                    script.Connection.Close();
                }
            }
            catch (Exception exConn)
            {
                SErrorManager.Exception()
                    .Throw(exConn, new TErrorInfo(GetType(), "CommitToDatabase")
                    , "Connection.Open() failed.\nConnection String is:\n{0}"
                    , script.Connection.ConnectionString);
            }
        }
        #endregion
    }
}
